今天主要會是來實做取用票券的 mobile app 。預計會使用 ReactNative 這樣的技術來達成。並且透過 expo 來實做對應的處理流程。
今天主要會先實做關於 layout 的部份。
Mobile application 負責的部份是處理用戶端的一些狀態處理。是一個系統中,用戶能夠直接操作到的部份,用戶對這個部份最能直接感受。
選用 expo 來實做 React Native 應用,好處是能夠使用跨平台的特性。並且有一系列的週邊配置。
基礎需求:
mkdir ticket-booking-app
cd ticket-booking-app
npx create-expo-app@latest
這邊需要先開啟模擬器
設置自己安裝 Android 位置,一般透過 Android Studio 會在 $HOME 目錄下
export ANDROID_HOME=/home/yuanyu/Android/Sdk
啟動 expo start --android
npm run android
如果成功,預設看到的畫面如下
只留下 navigation
目標 layout
主要的 layout 都在 app 資料夾下,登入之後的畫面預設有四個 navigation tab。各自對應到要執行的動作。
而程式資料夾結構如下:
最外層的 layout 是透過 _layout.tsx 與 login.tsx。這兩個依據狀態來 render。登入之後,就會是分 Events , Ticket , Scan Ticket 與 Setting 四個 component 各自擺放。
每個階層都有各自的 layout 。然後在各自一個自的功能切分 component 。 以下分別針對主要 layout 跟 tickets 的 layout 作元件講解其他則放到 repository 。
import { Slot } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
export default function Root() {
return (
<>
<StatusBar style='dark' />
{/* Authentication provider */}
<Slot />
{/* */}
</>
);
}
這是主要的頁面元件被包在一個大元件有兩個元件,基礎的 StatusBar 與 Slot 。 Slot 是 expo-router 的元件透過一些基礎 navigation 渲染畫面。
import { Redirect, Stack } from 'expo-router';
export default function AppLayout() {
// check from context if user is logged in
const isLogggedIn = true;
if (!isLogggedIn) {
return <Redirect href="/login" />
}
return <Stack screenOptions={{ headerShown: false }} />
}
這個 layout 會根據當下有沒有登入作不同顯示。
import { TabBarIcon } from '@/components/navigation/TabBarIcon';
import { Ionicons } from '@expo/vector-icons';
import { Tabs } from 'expo-router';
import { ComponentProps } from 'react';
import { Text } from 'react-native';
export default function TabLayout() {
const tabs = [
{
showFor: [],
name: '(events)',
displayName: 'Events',
icon: 'calendar',
options: {
headerShown: false
}
},
{
showFor: [],
name: '(tickets)',
displayName: 'My Tickets',
icon: 'ticket',
options: {
headerShown: false
}
},
{
showFor: [],
name: 'scan-ticket',
displayName: 'Scan Ticket',
icon: 'scan',
options: {
headerShown: true
}
},
{
showFor: [],
name: 'settings',
displayName: 'Settings',
icon: 'cog',
options: {
headerShown: true
}
},
];
return <Tabs>
{ tabs.map(tab => (
<Tabs.Screen
key={tab.name}
name={tab.name}
options={{
...tab.options,
headerTitle: tab.displayName,
// href: tab.showFor.includes()
tabBarLabel: ({focused}) => (
<Text style={{color: focused ? 'black': 'gray', fontSize: 12 }}>
{tab.displayName}
</Text>
),
tabBarIcon: ({focused}) => (
<TabBarIcon
name={tab.icon as ComponentProps<typeof Ionicons>['name']}
color={focused? 'black': 'gray'}
/>
)
}}
/>
)) }
</Tabs>;
}
顯示四的 tab 一些內容。
import { Stack } from 'expo-router';
export default function TicketsLayout() {
return (
<Stack screenOptions={{ headerBackTitle: 'Tickets' }}>
<Stack.Screen name='index' />
<Stack.Screen name='ticket/[id]'/>
</Stack>
)
}
基礎 ticket 導覽與其連結。
import { Stack } from 'expo-router';
export default function EventsLayout() {
return (
<Stack screenOptions={{ headerBackTitle: 'Events' }}>
<Stack.Screen name='index' />
<Stack.Screen name='new' />
<Stack.Screen name='event/[id]'/>
</Stack>
)
}
基礎 event 導覽與其連結。
npx expo start
本文目前提到的是有實做內容的 component 。其他對應,比如 event 與 ticket 資料夾下的 new.tsx 等等都需要自行實做回傳空的 component 。如下
export default function NewEvent() {
return <></>;
}
client code 雖然跟 nestjs 本身無關,但屬於這個系統的一部分。因此,會花篇幅來實做,並且與之前實做的 api 來作整合。完成一整個系統的開發。
這部份的實做細節都可以參考 expo 官方文件 去搜尋相關的元件實做。